using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Collections;

namespace block_explorer
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    class BlockExplorer : Microsoft.Xna.Framework.Game
    {
        private GraphicsDeviceManager mGraphicsDeviceManager;

        private InputManager mInputManager;
        private static ConsoleLog mConsole;
        private Hud mHud;
        private Scene mScene;
        private DeferredRenderer mDeferredRenderer;

        private SSAO mSSAO;
        private Fog mFog;

        public InputManager Input { get { return mInputManager; } }
        public static ConsoleLog Console { get { return mConsole; } }
        public Scene Scene { get { return mScene; } }

        int mSpotIdx;

        public BlockExplorer()
        {
            Content.RootDirectory = "Content";

            mGraphicsDeviceManager = new GraphicsDeviceManager(this);
            mGraphicsDeviceManager.PreferredBackBufferWidth = 1200;
            mGraphicsDeviceManager.PreferredBackBufferHeight = 675;
            mGraphicsDeviceManager.SynchronizeWithVerticalRetrace = false;
            //mGraphicsDeviceManager.PreferredBackBufferWidth = 1280;
            //mGraphicsDeviceManager.PreferredBackBufferHeight = 720;

            IsFixedTimeStep = false;

            mInputManager = new InputManager(this);
            mConsole = new ConsoleLog(this);
            mHud = new Hud(this);
            mScene = new Scene(this);
            mDeferredRenderer = new DeferredRenderer(this, mScene);

            mSSAO = new SSAO(mDeferredRenderer);
            mSSAO.Enabled = true;

            mFog = new Fog(mDeferredRenderer);
            mFog.Start = 50.0f;
            mFog.End = 200.0f;
            mFog.Color = new Color(0.7f, 0.75f, 0.77f);
            mFog.Enabled = true;

            mDeferredRenderer.AddPostProcess(mSSAO);
            mDeferredRenderer.AddPostProcess(mFog);

            mSpotIdx = -1;
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            const int consoleHeight = 150;
            mConsole.SetSize(new Rectangle(0, GraphicsDevice.Viewport.Height - consoleHeight, GraphicsDevice.Viewport.Width, consoleHeight));
            mConsole.DrawOrder = 1;

            Components.Add(mInputManager);
            Components.Add(mConsole);
            Components.Add(mHud);
            Components.Add(mDeferredRenderer);

            mScene.Initialize();

            //mScene.AmbientLight = new Color(0.60f, 0.65f, 0.67f);
            mScene.AmbientLight = new Color(0.30f, 0.35f, 0.37f);

            //DirectionalLight sun = new DirectionalLight(new Vector3(0.0f, -1, 0.0f), new Color(1.0f, 1.0f, 0.97f));
            //sun.Shadows = true;
            //mScene.AddLight(sun);

            //DirectionalLight sunAmb = new DirectionalLight(new Vector3(-0.5f, -1, -0.5f), new Color(0.8f, 0.8f, 0.77f));
            //sunAmb.Shadows = true;
            //mScene.AddLight(sunAmb);

            //PointLight point = new PointLight(new Vector3(-67.0f, 21.5f, -4.0f), Color.White, 10.0f);
            //point.Shadows = false;
            //mSpotIdx = mScene.AddLight(point);

            SpotLight spot = new SpotLight(new Vector3(-72.5f, 21.0f, -7.0f), new Vector3(0, 0, -1), Color.White, 30.0f, 27.0f, 3.0f);
            spot.Shadows = true;
            mSpotIdx = mScene.AddLight(spot);

            EntityCamera cameraEyes = new EntityCamera(Player.EyesOffset, GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight, 90.0f, 0.1f, 200.0f);
            ChaseCamera cameraChase = new ChaseCamera(Player.EyesOffset, new Vector3(0, 0, 2.5f), GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight, 90.0f, 0.1f, 200.0f);

            cameraEyes.Entity = mScene.Player;
            cameraChase.Entity = mScene.Player;

            mScene.AddCamera(cameraEyes);
            mScene.AddCamera(cameraChase);

            mInputManager.RegisterKeyEvent(new KeyEvent(Keys.Escape, KeyEvent.EventType.Pressed), delegate { Shutdown(); });
            mInputManager.RegisterKeyEvent(new KeyEvent(Keys.F1, KeyEvent.EventType.Pressed), delegate { mScene.ToggleNextCamera(); });
            mInputManager.RegisterKeyEvent(new KeyEvent(Keys.F2, KeyEvent.EventType.Pressed), delegate { mScene.Player.DebugShowBounds = !mScene.Player.DebugShowBounds; });
            mInputManager.RegisterKeyEvent(new KeyEvent(Keys.F3, KeyEvent.EventType.Pressed), delegate { mScene.Player.DebugShowPosition = !mScene.Player.DebugShowPosition; });
            mInputManager.RegisterKeyEvent(new KeyEvent(Keys.F4, KeyEvent.EventType.Pressed), delegate { mScene.Map.SectorBoundaries = !mScene.Map.SectorBoundaries; });
            mInputManager.RegisterKeyEvent(new KeyEvent(Keys.F5, KeyEvent.EventType.Pressed), delegate { mScene.Player.Massless = !mScene.Player.Massless; });
            mInputManager.RegisterKeyEvent(new KeyEvent(Keys.F6, KeyEvent.EventType.Pressed), delegate { mScene.Player.Solid = !mScene.Player.Solid; });
            mInputManager.RegisterKeyEvent(new KeyEvent(Keys.F7, KeyEvent.EventType.Pressed), delegate { mDeferredRenderer.DebugTextureRender = !mDeferredRenderer.DebugTextureRender; });
            mInputManager.RegisterKeyEvent(new KeyEvent(Keys.F8, KeyEvent.EventType.Pressed), delegate { mSSAO.Enabled = !mSSAO.Enabled; });
            mInputManager.RegisterGamepadEvent(Buttons.Back, delegate { Shutdown(); });

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            mScene.LoadContent();

            base.LoadContent();
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            base.UnloadContent();
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if (!IsActive)
                return;

            mScene.Update(gameTime);

            base.Update(gameTime);

            if (mSpotIdx != -1)
            {
                SpotLight torch = mScene.Lights[mSpotIdx] as SpotLight;
                if (Keyboard.GetState().IsKeyDown(Keys.Z))
                    torch.Angle += 10.0f * (float)gameTime.ElapsedGameTime.TotalSeconds;
                if (Keyboard.GetState().IsKeyDown(Keys.X))
                    torch.Angle -= 10.0f * (float)gameTime.ElapsedGameTime.TotalSeconds;

                torch.Position = mScene.Player.Position;
                torch.Direction = mScene.Player.Target - (mScene.Player.Position + Player.EyesOffset);

                //PointLight p = mScene.Lights[mSpotIdx] as PointLight;
                //p.Position = mScene.Player.Position;
            }
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.PresentationParameters.PresentationInterval = PresentInterval.Immediate;
            base.Draw(gameTime);
        }

        private void Shutdown()
        {
            mScene.Destroy();

            Exit();
        }
    }
}
